using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace GameFrameWork
{

    [DisallowMultipleComponent]
    [RequireComponent(typeof(RectTransform))]
    public class UIReuseList : UIBehaviour, IUIReuseList, IUserDataGettable
    {
        [SerializeField] RectTransform viewport;
        [SerializeField] RectTransform content;
        [SerializeField] UIReuseItem item;

        bool init;
        ResModule resModule;
        UIReuseItemPool itemPool;
        List<UIReuseContainer> containers;
        int dataCount;
        bool layoutDirty;
        UIReuseViewportDetection viewportDetection;
        UIReuseItem templateItem;
        UIVirtualLayoutGroup layoutGroup;
        IterateSafeList<IUpdate> updates;
        object userData;
        UIReuseList root;
        List<UIReuseList> nestReuseLists;
        bool dynamicSize;
        bool visible => gameObject.activeInHierarchy;
        UIReuseItemKey key;
        UIReuseItemKey Key
        {
            get
            {
                if (key == null)
                {
                    var flag = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static;
                    var fieldInfo = typeof(UIReuseItemKeys).GetField(item.name, flag);

                    Assert.IsNotNull(fieldInfo, $"UIReuseItemKeys can not found field, field name: {item.name}");
                    key = fieldInfo.GetValue(null) as UIReuseItemKey;
                }
                return key;
            }
        }

        int lastSelectIndex = -1;
        ReuseItemSelected onSelected = new();
        public ReuseItemSelected OnSelected => onSelected;
        public RectTransform Viewport => viewport;
        public RectTransform Content => content;

        public Vector2 ContentSize
        {
            get
            {
                if (!content) return Vector2.zero;
                return content.sizeDelta;
            }
        }

        public Vector2 GetAnchoredPosition(int index)
        {
            if (containers == null || containers.Count == 0 || containers.Count <= index)
                return Vector2.zero;

            return containers[index].GetAnchoredPosition();
        }

        public Vector3 GetPosition(int index)
        {
            if (containers == null || containers.Count == 0 || containers.Count <= index)
                return Vector2.zero;

            var container = containers[index];
            if (!InViewport(container))
            {
                Debug.LogWarning($"Index不在视口内, 复用列表获取不到世界坐标");
                return Vector2.zero;
            }

            return container.GetPosition();
        }

        public Vector2 GetLocalPosition(int index)
        {
            if (containers == null || containers.Count == 0 || containers.Count <= index)
                return Vector2.zero;

            var container = containers[index];
            if (!InViewport(container))
            {
                Debug.LogWarning($"Index不在视口内, 复用列表获取不到屏幕坐标");
                return Vector2.zero;
            }
            return container.GetLocalPosition();
        }

        object IUserDataGettable.userData => userData;

        bool Invalid => !(item && content);

        protected override void Awake()
        {
            base.Awake();
#if UNITY_EDITOR
            if (!Application.isPlaying)
                return;
#endif
            InternalInitialize();
        }

        protected override void OnEnable()
        {
            base.OnEnable();
#if UNITY_EDITOR
            if (!Application.isPlaying)
                return;
#endif
            if (Invalid) return;

            RefreshAll();
        }

        protected override void OnDisable()
        {
            base.OnDisable();
#if UNITY_EDITOR
            if (!Application.isPlaying)
                return;
#endif
            if (Invalid) return;

            DisableAllContainers();
        }


        protected override void OnDestroy()
        {
            base.OnDestroy();
#if UNITY_EDITOR
            if (!Application.isPlaying)
                return;
#endif
            if (Invalid) return;

            Clear();
        }

        void Update()
        {
            if (updates != null)
            {
                foreach (var update in updates)
                    update.Update(Time.deltaTime);
            }
        }

        void LateUpdate()
        {
#if UNITY_EDITOR
            if (!Application.isPlaying)
                return;
#endif
            if (Invalid) return;

            if (viewportDetection.TryUpdate())
            {
                // Must be called in LateUpdate to ensure that the layout is updated before the viewport is updated.
                UpdateViewport(OnItemInViewport, OnItemOutViewport, true);
            }

            if (layoutDirty && !CanvasUpdateRegistry.IsRebuildingLayout())
            {
                layoutDirty = false;
                layoutGroup.Layout();
            }
        }

        void InternalInitialize()
        {
            if (init) return;
            if (Invalid) return;
            layoutGroup = content.GetComponent<UIVirtualLayoutGroup>();
            Assert.IsTrue(layoutGroup, "UIReuseList里Content必须挂上UIVirtualLayoutGroup组件");

            init = true;
            resModule = new ResModule();
            containers = new List<UIReuseContainer>();
            updates = new IterateSafeList<IUpdate>();
            viewportDetection = new UIReuseViewportDetection(viewport, content);
            viewportDetection.TryUpdate();

            if (itemPool == null)
            {
                var pool = new UIReuseItemPool();
                SetItemPool(this, pool);
            }

            templateItem = itemPool.New(item, content);
            templateItem.transform.localPosition = Vector2.zero;
            templateItem.gameObject.SetActive(false);

            dynamicSize = item.TryGetComponent<ContentSizeFitter>(out var _);
        }

        public void Refresh(int count, object userdata = null, int selectIndex = -1)
        {
            if (!visible) return;
            if (Invalid)
            {
                Debug.LogError($"预制上Item或者Content没挂");
                return;
            }
            Assert.IsTrue(dataCount >= 0, "Count必须大于或等于0！！");
            Assert.IsTrue(selectIndex < count, "selectIndex必须小于count！！");

            dataCount = count;
            userData = userdata;
            lastSelectIndex = selectIndex;

            Flush();
            RefreshAll();
        }

        public void RefreshAll()
        {
            if (!visible) return;
            if (Invalid) return;
            if (containers == null) return;

            viewportDetection.TryUpdate();

            UpdateViewport(OnItemInViewport, OnItemOutViewport, false);
        }


        public int Count
        {
            get => dataCount;
        }

        void ForceLayout()
        {
            if (CanvasUpdateRegistry.IsRebuildingLayout())
                layoutDirty = true;
            else
                layoutGroup.Layout();
        }

        void Flush()
        {
            if (dataCount == 0)
            {
                Clean();
                return;
            }

            InternalInitialize();
            ResizeContainers();
            LayoutContainers();
        }


        void ResizeContainers()
        {
            containers ??= new List<UIReuseContainer>();
            int count = containers.Count;
            if (count > dataCount)
            {
                int i = dataCount;
                while (i++ < count)
                {
                    int lastIndex = containers.Count - 1;
                    var container = containers[lastIndex];
                    if (container.IsOccupied)
                    {
                        var view = container.View;

                        container.Disable();
                        container.View.SetActive(false);
                        container.View = null;
                        itemPool.Recycle(view);
                    }

                    containers.RemoveAt(lastIndex);
                    layoutGroup.RemoveLayoutElement(container);
                    if (container.Ctrl is IUpdate update) updates.Remove(update);
                }
            }
            else
            {
                int i = count;

                while (i < dataCount)
                {
                    var ctrl = Key.CtrlFactory.Invoke();

                    var container = new UIReuseContainer(i, this, ctrl, resModule, content, viewport);

                    containers.Add(container);

                    layoutGroup.AddLayoutElement(container);

                    i++;
                }
            }
        }

        void LayoutContainers()
        {
            templateItem.SetActive(true);
            for (int i = 0; i < dataCount; i++)
            {
                var container = containers[i];

                var view = container.View;

                var template = view != null ? view : templateItem;

                UpdateDynamicSize(container, template, dynamicSize);

                container.View = view;
            }
            templateItem.SetActive(false);
            templateItem.name = item.name;
            layoutGroup.Layout();
        }

        void UpdateDynamicSize(UIReuseContainer container, UIReuseItem view, bool dynamicSize)
        {
            if (dynamicSize)
            {
                container.View = view;
                container.Enable();
                container.Refresh();
                container.View.Layout(true);
                container.CalculateTransformation(view.RectTransform);
                container.Disable();
                container.View = null;
            }
            else
            {
                container.CalculateTransformation(view.RectTransform);
            }
        }


        void DisableAllContainers()
        {
            for (int i = containers.Count - 1; i >= 0; i--)
            {
                var container = containers[i];

                DisableSingleContainer(container);
            }
        }

        void UpdateViewport(Action<UIReuseContainer, bool> enableAction,
                Action<UIReuseContainer, bool> disableAction, bool callInUpdate)
        {
            var viewportbounds = viewportDetection.GetVirtualBounds();
            var worldToViewportMatrix = viewport.worldToLocalMatrix;
            var count = containers.Count;
            for (int i = 0; i < count; i++)
            {
                UIReuseContainer container = containers[i];
                container.UpdateBounds();
                if (viewportbounds.Intersects(container.Bound))
                {
                    enableAction?.Invoke(container, callInUpdate);
                }
                else
                {
                    disableAction?.Invoke(container, callInUpdate);
                }
            }
        }

        bool InViewport(UIReuseContainer container)
        {
            var viewportbounds = viewportDetection.GetVirtualBounds();
            container.UpdateBounds();
            return viewportbounds.Intersects(container.Bound);
        }

        void OnItemInViewport(UIReuseContainer container, bool callInUpdate)
        {
            if (container.IsOccupied)
            {
                if (!callInUpdate)
                    container.Refresh();
            }
            else
            {
                EnableSingleContainer(container);
                container.Refresh();
            }

            if (dynamicSize) container.View.Layout(true);

            container.Apply();
        }

        void OnItemOutViewport(UIReuseContainer container, bool callInUpdate)
        {
            if (!container.IsOccupied)
                return;

            DisableSingleContainer(container);
        }

        void DeleteContainers()
        {
            if (containers == null || containers.Count == 0) return;

            for (int i = containers.Count - 1; i >= 0; i--)
            {
                var container = containers[i];

                DisableSingleContainer(container);

                layoutGroup.RemoveLayoutElement(container);
            }
            containers.Clear();
            layoutGroup.Layout();
        }

        void EnableSingleContainer(UIReuseContainer container)
        {
            if (container != null && !container.IsOccupied)
            {
                var view = itemPool.New(item, content);
                container.View = view;
                container.View.SetActive(true);
                container.Enable();
                if (container.Ctrl is IUpdate update) updates.Add(update);
            }
        }

        void DisableSingleContainer(UIReuseContainer container)
        {
            if (container != null && container.IsOccupied)
            {
                var view = container.View;
                container.Disable();
                container.View.SetActive(false);
                container.View = null;
                if (container.Ctrl is IUpdate update) updates.Remove(update);
                itemPool.Recycle(view);
            }
        }


        void Clean()
        {
            lastSelectIndex = -1;
            dataCount = 0;
            layoutDirty = false;
            updates?.Clear();
            DeleteContainers();
        }

        void Clear()
        {
            Clean();
            itemPool?.ReleaseAll();
            resModule.Dispose();
        }

        void SetItemPool(UIReuseList reuselist, UIReuseItemPool pool, bool recursion = true)
        {
            if (recursion && reuselist.item)
            {
                var nestReuselists = reuselist.item.GetNestReuseList();
                foreach (var nest in nestReuselists)
                    SetItemPool(nest, pool);
            }
            reuselist.itemPool = pool;
        }


        /// <summary>
        /// Invoke by internal, don't call it.
        /// </summary>
        /// <param name="index"></param>
        public void OnItemClick(int index)
        {
            Select(index);
        }

        public void Select(int index)
        {
            if (lastSelectIndex == index) return;

            try
            {
                //GameEntry.Sound.PlayUISound(Constant.Music.Click);

                onSelected?.Invoke(lastSelectIndex, index);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
            finally
            {
                lastSelectIndex = index;
            }

            RefreshAll();
        }

        public bool IsSelected(int index)
        {
            return lastSelectIndex == index;
        }
    }
}

